
#include <dev.h>
#include "pci.h"

/* ------------------------------------------------------------------------- */

#define PCI_HOST						pci_make_id(0, 0, 0)			/* 0x80000000 */
#define PCI_MEM							pci_make_id(0, 0, 3)			/* 0x80000300 */
#define PCI_ISA							pci_make_id(0, 1, 0)			/* 0x80000800 */
#define PCI_SMBUS						pci_make_id(0, 1, 1)			/* 0x80000900 */
#define PCI_USB1						pci_make_id(0, 2, 0)			/* 0x80001000 */
#define PCI_USB2						pci_make_id(0, 3, 0)			/* 0x80001800 */
#define PCI_NET							pci_make_id(0, 4, 0)			/* 0x80002000 */
#define PCI_AUD							pci_make_id(0, 5, 0)			/* 0x80002800 */
#define PCI_ACI							pci_make_id(0, 6, 0)			/* 0x80003000 */
#define PCI_IDE							pci_make_id(0, 9, 0)			/* 0x80008400 */
#define PCI_AGP							pci_make_id(0, 30, 0)			/* 0x8000f000 */
#define PCI_VGA							pci_make_id(1, 0, 0)			/* 0x80010000 */

#define pci_make_id(bus, dev, fn)		(0x80000000 | ((bus) << 16) | ((dev) << 11) | ((fn) << 8))
#define pci_dev(id)						((id) & 0x80ffff00)
#define pci_reg(id)						((id) & 0x000000fc)

/* ------------------------------------------------------------------------- */

static const uint32 conf_w_mask[][16] = {
	{
		0x00000000, 0x000003ff, 0x00000000, 0x4000ffff,
		0xfffffffc, 0xfffffffc, 0xfffffffc, 0xfffffffc,
		0xfffffffc, 0xfffffffc, 0x00000000, 0x00000000,
		0xfffff801, 0x00000000, 0x00000000, 0x000000ff,
	}, {
		0x00000000, 0x000003ff, 0x00000000, 0x4000ffff,
		0xfffffffc, 0xfffffffc, 0xffffffff, 0x0000f0f0,
		0xfff0fff0, 0xfff0fff0, 0x00000000, 0x00000000,
		0x00000000, 0x00000000, 0xfffff801, 0x000000ff,
	},
};

static const uint32 conf_c_mask[] = {
	0x00000000, 0x00000000, 0xf9000000, 0x00000000,
	0x00000000, 0x00000000, 0x00000000, 0x00000000,
	0x00000000, 0x00000000, 0x00000000, 0x00000000,
	0x00000000, 0x00000000, 0x00000000, 0x00000000,
};

/* ------------------------------------------------------------------------- */

static int dev_no;
static uint32 config_addr;
static pci_dev_t *pci_dev_table[PCI_DEV_NUM];

static const pci_dev_t * fastcall addressing_io_space(uint16 port, uint *map, uint8 *ca);
static const pci_dev_t * fastcall addressing_mem_space(uint32 addr, uint *map, uint32 *ca);

/* ------------------------------------------------------------------------- */

void fastcall pci_init(void)
{
	dev_no = -1;
	config_addr = 0;

	pci_dev_table[PCI_DEV_HOST] = get_host_device();
	pci_dev_table[PCI_DEV_MEM] = get_mem_device();
	pci_dev_table[PCI_DEV_ISA] = get_isa_device();
	pci_dev_table[PCI_DEV_SMBUS] = get_smbus_device();
	pci_dev_table[PCI_DEV_USB1] = get_usb1_device();
	pci_dev_table[PCI_DEV_USB2] = get_usb2_device();
	pci_dev_table[PCI_DEV_NET] = get_net_device();
	pci_dev_table[PCI_DEV_AUD] = get_aud_device();
	pci_dev_table[PCI_DEV_ACI] = get_aci_device();
	pci_dev_table[PCI_DEV_IDE] = get_ide_device();
	pci_dev_table[PCI_DEV_AGP] = get_agp_device();
	pci_dev_table[PCI_DEV_VGA] = get_vga_device();
}

uint32 fastcall pci_get_config_addr(void)
{
	return config_addr;
}

void fastcall pci_set_config_addr(uint32 addr, uint32 mask)
{
	config_addr = combine_data(config_addr, addr, mask);

	switch (pci_dev(config_addr)) {
	case PCI_HOST:
		dev_no = PCI_DEV_HOST;
		break;
	case PCI_MEM:
		dev_no = PCI_DEV_MEM;
		break;
	case PCI_ISA:
		dev_no = PCI_DEV_ISA;
		break;
	case PCI_SMBUS:
		dev_no = PCI_DEV_SMBUS;
		break;
	case PCI_USB1:
		dev_no = PCI_DEV_USB1;
		break;
	case PCI_USB2:
		dev_no = PCI_DEV_USB2;
		break;
	case PCI_NET:
		dev_no = PCI_DEV_NET;
		break;
	case PCI_AUD:
		dev_no = PCI_DEV_AUD;
		break;
	case PCI_ACI:
		dev_no = PCI_DEV_ACI;
		break;
	case PCI_IDE:
		dev_no = PCI_DEV_IDE;
		break;
	case PCI_AGP:
		dev_no = PCI_DEV_AGP;
		break;
	case PCI_VGA:
		dev_no = PCI_DEV_VGA;
		break;
	default:
		assert(0);
		return;
	}
}

uint32 fastcall pci_get_config_data(void)
{
	uint8 reg;
	uint32 data;
	const uint32 *ptr;
	const pci_dev_t *dev;

	data = 0xffffffff;

	reg = pci_reg(config_addr);
	if (reg >= PCI_CONFIG_SIZE)
		return data;

	dev = pci_dev_table[dev_no];
	assert(dev);
	if (!dev)
		return data;

#ifndef NDEBUG
	write_log("[%s] get conf : %02Xh", dev->name, reg);
#endif

	reg >>= 2;
	ptr = (uint32 *)&dev->conf + reg;
	data = *ptr;

	return data;
}

void fastcall pci_set_config_data(uint32 data, uint32 mask)
{
	uint8 reg;
	const pci_dev_t *dev;

	reg = pci_reg(config_addr);
	if (reg >= PCI_CONFIG_SIZE)
		return;

	dev = pci_dev_table[dev_no];
	assert(dev);
	if (!dev)
		return;

	pci_config_header(dev, reg, data, mask);

#ifndef NDEBUG
	if (mask == 0xffffffff)
		write_log("[%s] set conf : %02Xh = %08Xh", dev->name, reg, data);
	else
		write_log("[%s] set conf : %02Xh = %08Xh & %08Xh", dev->name, reg, data, mask);
#endif

	dev->config(reg, data, mask);
}

size_t fastcall pci_io_mapping_read(uint16 port, void *buf, size_t size)
{
	size_t len;
	uint map = 0;
	uint8 ca = 0;
	uint8 *ptr = buf;
	const pci_dev_t *dev;

	memset(buf, 0xff, size);

	while (size > 0) {

		dev = addressing_io_space(port, &map, &ca);

		if (!dev)
			break;

		len = dev->read_map(map, ca, ptr, size);
#ifndef NDEBUG
		if (map == 1 && strcmp(dev->name, "SMBUS")) {
			switch (len)
			{
			case 1:
				write_log("[%s] read io (%d) : %04Xh = %02Xh", dev->name, map, ca, *ptr);
				break;
			case 2:
				write_log("[%s] read io (%d) : %04Xh = %04Xh", dev->name, map, ca, *(uint16 *)ptr);
				break;
			case 4:
				write_log("[%s] read io (%d) : %04Xh = %08Xh", dev->name, map, ca, *(uint32 *)ptr);
				break;
			default:
				assert(0);
				break;
			}
		}
#endif
		size -= len;
		ptr += len;
	}

	return ptr - (uint8 *)buf;
}

size_t fastcall pci_io_mapping_write(uint16 port, const void *buf, size_t size)
{
	size_t len;
	uint map = 0;
	uint8 ca = 0;
	const uint8 *ptr = buf;
	const pci_dev_t *dev;

	while (size > 0) {

		dev = addressing_io_space(port, &map, &ca);

		if (!dev)
			break;

		len = dev->write_map(map, ca, ptr, size);
#ifndef NDEBUG
		if (map == 1 && strcmp(dev->name, "SMBUS")) {
			switch (len)
			{
			case 1:
				write_log("[%s] write io (%d) : %04Xh = %02Xh", dev->name, map, ca, *ptr);
				break;
			case 2:
				write_log("[%s] write io (%d) : %04Xh = %04Xh", dev->name, map, ca, *(uint16 *)ptr);
				break;
			case 4:
				write_log("[%s] write io (%d) : %04Xh = %08Xh", dev->name, map, ca, *(uint32 *)ptr);
				break;
			default:
				assert(0);
				break;
			}
		}
#endif

		size -= len;
		ptr += len;
	}

	return ptr - (const uint8 *)buf;
}

size_t fastcall pci_mem_mapping_read(uint32 addr, void *buf, size_t size)
{
	size_t len;
	uint map = 0;
	uint32 ca = 0;
	uint8 *ptr = buf;
	const pci_dev_t *dev;

	memset(buf, 0xff, size);

	while (size > 0) {

		dev = addressing_mem_space(addr, &map, &ca);

		if (!dev)
			break;

		len = dev->read_map(map, ca, ptr, size);
#ifndef NDEBUG
		switch (len)
		{
		case 1:
			write_log("[%s] read memory (%d) : %08Xh = %02Xh", dev->name, map, ca, *ptr);
			break;
		case 2:
			write_log("[%s] read memory (%d) : %08Xh = %04Xh", dev->name, map, ca, *(uint16 *)ptr);
			break;
		case 4:
			write_log("[%s] read memory (%d) : %08Xh = %08Xh", dev->name, map, ca, *(uint32 *)ptr);
			break;
		default:
			assert(0);
			break;
		}
#endif
		size -= len;
		ptr += len;
	}

	return ptr - (uint8 *)buf;
}

size_t fastcall pci_mem_mapping_write(uint32 addr, const void *buf, size_t size)
{
	size_t len;
	uint map = 0;
	uint32 ca = 0;
	const uint8 *ptr = buf;
	const pci_dev_t *dev;

	while (size > 0) {

		dev = addressing_mem_space(addr, &map, &ca);

		if (!dev)
			break;

		len = dev->write_map(map, ca, ptr, size);
#ifndef NDEBUG
		switch (len)
		{
		case 1:
			write_log("[%s] write memory (%d) : %08Xh = %02Xh", dev->name, map, ca, *ptr);
			break;
		case 2:
			write_log("[%s] write memory (%d) : %08Xh = %04Xh", dev->name, map, ca, *(uint16 *)ptr);
			break;
		case 4:
			write_log("[%s] write memory (%d) : %08Xh = %08Xh", dev->name, map, ca, *(uint32 *)ptr);
			break;
		default:
			assert(0);
			break;
		}
#endif
		size -= len;
		ptr += len;
	}

	return ptr - (const uint8 *)buf;
}

void fastcall pci_config_header(const pci_dev_t *dev, uint8 reg, uint32 data, uint32 mask)
{
	size_t size;
	uint8 base_max;
	uint32 w_mask, c_mask;
	uint32 *ptr;

	if (reg >= PCI_CONFIG_HEADER_SIZE)
		return;

	reg >>= 2;

	w_mask = conf_w_mask[dev->conf.header_type & 0x01][reg];
	c_mask = conf_c_mask[reg];
	data = (data & w_mask) & ~(data & c_mask);

	ptr = (uint32 *)&dev->conf + reg;
	*ptr = combine_data(*ptr, data, w_mask | c_mask);

	switch (dev->conf.header_type) {
	case 0x00:
		base_max = 10;
		break;
	case 0x01:
		base_max = 6;
		break;
	default:
		return;
	}

	if (reg < 4 || reg >= base_max)
		return;

	size = dev->map_size(reg - 4);
	if (size <= 0)
		return;

	data = *ptr & ~(size - 1);
	*ptr = combine_data(*ptr, data, w_mask | c_mask);
}

/* ------------------------------------------------------------------------- */

static const pci_dev_t * fastcall addressing_io_space(uint16 port, uint *map, uint8 *ca)
{
	int i, j, num, offset;
	size_t size;
	const uint32 *base;
	const pci_dev_t *dev;

	for (i = 0; i < PCI_DEV_NUM; i++) {

		dev = pci_dev_table[i];
		assert(dev);
		if (!dev)
			continue;

		if (!(dev->conf.command & 0x0001))
			continue;

		switch (dev->conf.header_type) {
		case 0x00:
			num = 6;
			base = dev->conf.hdr.type0.base_addr;
			break;
		case 0x01:
			num = 2;
			base = dev->conf.hdr.type1.base_addr;
			break;
		default:
			continue;
		}

		for (j = 0; j < num; j++) {

			size = dev->map_size(j);
			if (size <= 0)
				continue;

			if (!(base[j] & 0x00000001))
				continue;

			offset = port - (base[j] & 0xfffffffc);
			if (offset < 0 || (uint)offset >= size)
				continue;

			*ca = offset;
			*map = j;
			return dev;
		}
	}

	return NULL;
}

static const pci_dev_t * fastcall addressing_mem_space(uint32 addr, uint *map, uint32 *ca)
{
	int i, j, num, offset;
	size_t size;
	const uint32 *base;
	const pci_dev_t *dev;

	for (i = 0; i < PCI_DEV_NUM; i++) {

		dev = pci_dev_table[i];
		assert(dev);
		if (!dev)
			continue;

		if (!(dev->conf.command & 0x0002))
			continue;

		switch (dev->conf.header_type) {
		case 0x00:
			num = 6;
			base = dev->conf.hdr.type0.base_addr;
			break;
		case 0x01:
			num = 2;
			base = dev->conf.hdr.type1.base_addr;
			break;
		default:
			continue;
		}

		for (j = 0; j < num; j++) {

			size = dev->map_size(j);
			if (size <= 0)
				continue;

			if (base[j] & 0x00000001)
				continue;

			offset = addr - (base[j] & 0xfffffff0);
			if (offset < 0 || (uint)offset >= size)
				continue;

			*ca = offset;
			*map = j;
			return dev;
		}
	}

	return NULL;
}

/* ------------------------------------------------------------------------- */
